#include "util.h"

#include <osg/View>

OSGRenderResizedCallback::OSGRenderResizedCallback(osg::Camera* masterCamera) :mMasterCamera(masterCamera)
{

}

void OSGRenderResizedCallback::resizedImplementation(osg::GraphicsContext* gc, int x, int y, int width, int height)
{
    const osg::GraphicsContext::Traits* traits = gc->getTraits();
    if (traits == NULL) return;

    double widthChangeRatio = double(width) / double(traits->width);
    double heigtChangeRatio = double(height) / double(traits->height);
    double aspectRatioChange = widthChangeRatio / heigtChangeRatio;

    osg::ref_ptr<osg::Camera> camera = NULL;
    if (mMasterCamera.lock(camera))
    {
        osg::Viewport* viewport = camera->getViewport();
        if (viewport)
        {
            if (viewport->x() == 0 && viewport->y() == 0 &&
                viewport->width() >= traits->width && viewport->height() >= traits->height)
            {
                viewport->setViewport(0, 0, width, height);
            }
            else
            {
                viewport->x() = static_cast<osg::Viewport::value_type>(double(viewport->x())*widthChangeRatio);
                viewport->y() = static_cast<osg::Viewport::value_type>(double(viewport->y())*heigtChangeRatio);
                viewport->width() = static_cast<osg::Viewport::value_type>(double(viewport->width())*widthChangeRatio);
                viewport->height() = static_cast<osg::Viewport::value_type>(double(viewport->height())*heigtChangeRatio);
            }

            // update texture image
            osg::Camera::BufferAttachmentMap& attachMap = camera->getBufferAttachmentMap();
            osg::Camera::BufferAttachmentMap::iterator citr = attachMap.find(osg::Camera::COLOR_BUFFER0);
            if (citr != attachMap.cend())
            {
                osg::Camera::Attachment& attach = citr->second;
                osg::Image* img = attach._image;
                if (img)
                {
                    int s = viewport->width() - viewport->x();
                    int t = viewport->height() - viewport->y();
                    if (img->s() != s || img->t() != t)
                    {
                        img->allocateImage(s, t, img->r(), img->getInternalTextureFormat(), img->getDataType(), img->getPacking());
                        camera->dirtyAttachmentMap();
                    }
                }
            }
        }

        // if aspect ratio adjusted change the project matrix to suit.
        if (aspectRatioChange != 1.0)
        {
            osg::View* view = camera->getView();
            osg::View::Slave* slave = view ? view->findSlaveForCamera(camera) : 0;

            if (slave)
            {
                if (camera->getReferenceFrame() == osg::Transform::RELATIVE_RF)
                {
                    switch (view->getCamera()->getProjectionResizePolicy())
                    {
                    case(osg::Camera::HORIZONTAL) : slave->_projectionOffset *= osg::Matrix::scale(1.0 / aspectRatioChange, 1.0, 1.0); break;
                    case(osg::Camera::VERTICAL) : slave->_projectionOffset *= osg::Matrix::scale(1.0, aspectRatioChange, 1.0); break;
                    default: break;
                    }
                }
                else
                {
                    switch (camera->getProjectionResizePolicy())
                    {
                    case(osg::Camera::HORIZONTAL) : camera->getProjectionMatrix() *= osg::Matrix::scale(1.0 / aspectRatioChange, 1.0, 1.0); break;
                    case(osg::Camera::VERTICAL) : camera->getProjectionMatrix() *= osg::Matrix::scale(1.0, aspectRatioChange, 1.0); break;
                    default: break;
                    }
                }
            }
            else
            {
                osg::Camera::ProjectionResizePolicy policy = view ? view->getCamera()->getProjectionResizePolicy() : camera->getProjectionResizePolicy();
                switch (policy)
                {
                case(osg::Camera::HORIZONTAL) : camera->getProjectionMatrix() *= osg::Matrix::scale(1.0 / aspectRatioChange, 1.0, 1.0); break;
                case(osg::Camera::VERTICAL) : camera->getProjectionMatrix() *= osg::Matrix::scale(1.0, aspectRatioChange, 1.0); break;
                default: break;
                }

                osg::Camera* master = view ? view->getCamera() : 0;
                if (view && camera == master)
                {
                    for (unsigned int i = 0; i < view->getNumSlaves(); ++i)
                    {
                        osg::View::Slave& child = view->getSlave(i);
                        if (child._camera.valid() && child._camera->getReferenceFrame() == osg::Transform::RELATIVE_RF)
                        {
                            // scale the slaves by the inverse of the change that has been applied to master, to avoid them be
                            // scaled twice (such as when both master and slave are on the same GraphicsContexts) or by the wrong scale
                            // when master and slave are on different GraphicsContexts.
                            switch (policy)
                            {
                            case(osg::Camera::HORIZONTAL) : child._projectionOffset *= osg::Matrix::scale(aspectRatioChange, 1.0, 1.0); break;
                            case(osg::Camera::VERTICAL) : child._projectionOffset *= osg::Matrix::scale(1.0, 1.0 / aspectRatioChange, 1.0); break;
                            default: break;
                            }
                        }
                    }
                }
            }
        }
    }

    gc->resizedImplementation(x, y, width, height);
}